package com.xiaoxiao.xmvc.servlet;

import com.xiaoxiao.xmvc.annotation.*;
import com.xiaoxiao.xmvc.controller.UserController;

import javax.servlet.ServletConfig;
import javax.servlet.ServletException;
import javax.servlet.annotation.WebInitParam;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.File;
import java.io.IOException;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.net.URL;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

/**
 * ${DESCRIPTION}
 * DisapatchServlet
 *
 * @author muxiaoyu
 * @create 2018/09/20 11:40
 **/
@WebServlet(name = "dispatchServlet", urlPatterns = "/*", loadOnStartup = 1, initParams = {@WebInitParam(name = "base-package", value = "com.xiaoxiao.xmvc")})
public class DispatchServlet extends HttpServlet {
    //base package
    private String basePackage = "";
    //base package下所有类的全限定名
    private List<String> packageNames = new ArrayList<String>();
    //注解实例化-->key：实例化的名称，value：实例化的对象，注入用
    private Map<String, Object> instanceMap = new HashMap<>();
    //base package下所有注解的全限定名
    private Map<String, String> nameMap = new HashMap<>();
    //URL和方法的映射关系 方法调用链
    private Map<String, Method> urlMethodMap = new HashMap<>();
    //Method和全限定名映射关系-->通过Method找到方法对象并利用反射执行
    private Map<Method, String> methodPackageMap = new HashMap<>();

    /*
        在init中，主要完成：
        第一：我们应该去扫描基包下的类，得到信息A。
        第二：对于@Controller/@Service/@Repository注解而言，我们需要拿到对应的名称，并初始化它们修饰的类，形成映射关系B。
        第三：我们还得扫描类中的字段，如果发现有@Qualifier的话，我们需要完成注入。
        第四：我们还需要扫描@RequestMapping，完成URL到某一个Controller的某一个方法上的映射关系C。
     */
    @Override
    public void init(ServletConfig config) throws ServletException {
        basePackage = config.getInitParameter("base-package");

        try {
            scanBasePackage(basePackage);
            instance(packageNames);
            springIOC();
            handlerURLMethodMap();
        } catch (ClassNotFoundException e) {
            e.printStackTrace();
        } catch (IllegalAccessException e) {
            e.printStackTrace();
        } catch (InstantiationException e) {
            e.printStackTrace();
        }

    }

    //4
    private void handlerURLMethodMap() throws ClassNotFoundException {
        if (packageNames.size() < 1) {
            return;
        }
        for (String packageName : packageNames) {
            Class<?> cls = Class.forName(packageName);
            if (cls.isAnnotationPresent(Controller.class)) {
                Method[] methods = cls.getMethods();
                StringBuffer baseUrl = new StringBuffer();
                if (cls.isAnnotationPresent(RequestMapping.class)) {
                    RequestMapping requestMapping = cls.getAnnotation(RequestMapping.class);
                    baseUrl.append(requestMapping.value());
                }
                for (Method method : methods) {
                    if (method.isAnnotationPresent(RequestMapping.class)) {
                        RequestMapping requestMapping = method.getAnnotation(RequestMapping.class);
                        baseUrl.append(requestMapping.value());
                        urlMethodMap.put(baseUrl.toString(), method);
                        methodPackageMap.put(method, packageName);
                    }
                }
            }
        }
    }

    //3
    private void springIOC() throws IllegalAccessException {
        for (Map.Entry<String, Object> entry : instanceMap.entrySet()) {
            Field[] declaredFields = entry.getValue().getClass().getDeclaredFields();
            for (Field declaredField : declaredFields) {
                if (declaredField.isAnnotationPresent(Qualifier.class)) {
                    String name = declaredField.getAnnotation(Qualifier.class).value();
                    declaredField.setAccessible(true);
                    declaredField.set(entry.getValue(), instanceMap.get(name));
                }
            }
        }
    }

    //2
    private void instance(List<String> packageNames) throws ClassNotFoundException, IllegalAccessException, InstantiationException {
        if (packageNames.size() < 1) {
            return;
        }
        for (String name : packageNames) {
            Class<?> cls = Class.forName(name);
            if (cls.isAnnotationPresent(Controller.class)) {
                Controller controller = cls.getAnnotation(Controller.class);
                String controllerName = controller.value();
                instanceMap.put(controllerName, cls.newInstance());
                nameMap.put(name, controllerName);
                System.out.println("Controller:" + name + ",value:" + controllerName);
            }
            if (cls.isAnnotationPresent(Service.class)) {
                Service service = cls.getAnnotation(Service.class);
                String serviceName = service.value();
                instanceMap.put(serviceName, cls.newInstance());
                nameMap.put(name, serviceName);
                System.out.println("Service:" + name + ",value:" + serviceName);
            }
            if (cls.isAnnotationPresent(Repository.class)) {
                Repository service = cls.getAnnotation(Repository.class);
                String repositoryName = service.value();
                instanceMap.put(repositoryName, cls.newInstance());
                nameMap.put(name, repositoryName);
                System.out.println("Repository:" + name + ",value:" + repositoryName);
            }
        }
    }

    //1
    private void scanBasePackage(String basePackage) {
        //基包是X.Y.Z的形式，而URL是X/Y/Z的形式，需要转换。
        URL url = this.getClass().getClassLoader().getResource(basePackage.replaceAll("\\.", "/"));
        File basePackageFile = new File(url.getPath());
        System.out.println("scan:" + basePackageFile);
        File[] childFiles = basePackageFile.listFiles();
        for (File childFile : childFiles) {
            if (childFile.isDirectory()) {
                scanBasePackage(basePackage + "." + childFile.getName());
            } else if (childFile.isFile()) {
                packageNames.add(basePackage + "." + childFile.getName().split("\\.")[0]);
            }
        }
    }
    //5

    @Override
    protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        doPost(req, resp);
    }

    @Override
    protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        String requestURI = req.getRequestURI();
        String contextPath = req.getContextPath();
        String path = requestURI.replaceAll(contextPath, "");
        Method method = urlMethodMap.get(path);
        if (method != null) {
            String packageName = methodPackageMap.get(method);
            String controllerName = nameMap.get(packageName);

            UserController userController = (UserController) instanceMap.get(controllerName);
            try {
                method.setAccessible(true);
                method.invoke(userController);
            } catch (IllegalAccessException e) {
                e.printStackTrace();
            } catch (InvocationTargetException e) {
                e.printStackTrace();
            }
        }
    }
}
